import G6 from '@antv/g6';
import { EnumModelType, EnumToolBarType, EnumCanvasContextMenu, EnumNodeContextMenu, EnumEdgeContextMenu, EnumAllContextMenu } from './constant/index';
import lodash from 'lodash';
import { MessageBox } from 'element-ui';

/* 默认节点label最大宽度*/
const LABEL_MAX_WIDTH = 110;
/* 默认节点label字号*/
let LABEL_FONT_SIZE = 12;

export default class G6Utils {
  /**
   * 内置枚举
   * @type {{EnumAllContextMenu: {}, EnumNodeContextMenu: {}, EnumCanvasContextMenu: {refreshLayout, undo, redo, clear}, EnumEdgeContextMenu: {}, EnumToolBarType: {undo, redo, refreshLayout, autoZoom}, EnumModelType: {default, edit}}}
   */
  static DefaultConstant ={
    EnumAllContextMenu,
    EnumNodeContextMenu,
    EnumCanvasContextMenu,
    EnumEdgeContextMenu,
    EnumToolBarType,
    EnumModelType
  };

  /**
   * 以换行的方式处理长文本
   * @param str
   * @param maxWidth
   * @param fontSize
   * @returns {string}
   */
  static breakingString(str = '', maxWidth = LABEL_MAX_WIDTH, fontSize = LABEL_FONT_SIZE) {
    let currentWidth = 0;
    let res = str;
    const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
    str.split('').forEach((letter, i) => {
      if (currentWidth > maxWidth) return;
      if (pattern.test(letter)) {
        // Chinese charactors
        currentWidth += fontSize;
      } else {
        // get the width of single letter according to the fontSize
        currentWidth += G6.Util.getLetterWidth(letter, fontSize);
      }
      if (currentWidth > maxWidth) {
        res = `${str.substr(0, i)}\n${str.substr(i)}`;
      }
    });
    return res;
  }

  /**
   * 以...的方式处理长文本
   * @param str
   * @param maxWidth
   * @param fontSize
   * @returns {string}
   */
  static ellipsisString(str = '', maxWidth = LABEL_MAX_WIDTH, fontSize = LABEL_FONT_SIZE) {
    const ellipsis = '...';
    const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
    let currentWidth = 0;
    let res = str;
    const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
    str.split('').forEach((letter, i) => {
      if (currentWidth > maxWidth - ellipsisLength) return;
      if (pattern.test(letter)) {
        // Chinese charactors
        currentWidth += fontSize;
      } else {
        // get the width of single letter according to the fontSize
        currentWidth += G6.Util.getLetterWidth(letter, fontSize);
      }
      if (currentWidth > maxWidth - ellipsisLength) {
        res = `${str.substr(0, i)}${ellipsis}`;
      }
    });
    return res;
  }

  /**
   * 获取文字长度
   * @param str
   * @param fontSize
   * @returns {number}
   */
  static getLabelWidth(str = '', fontSize = LABEL_FONT_SIZE) {
    let currentWidth = 0;
    const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
    str.split('').forEach((letter, i) => {
      if (pattern.test(letter)) {
        // Chinese charactors
        currentWidth += fontSize;
      } else {
        currentWidth += G6.Util.getLetterWidth(letter, fontSize);
      }
      // get the width of single letter according to the fontSize
    });
    return currentWidth;
  }

  constructor() {
    this.graph = null;
  }

  /**
   * 初始化编辑器
   * @param container
   * @param options
   * @returns {*}
   */
  initEditor(container = 'editorContainer', options = {}) {
    const grid = new G6.Grid();
    this.graph = new G6.Graph(Object.assign({
      container,
      animate: true,
      // linkCenter: true,
      defaultEdge: {
        // type: 'cubic-vertical',
        type: 'cubic-horizontal',
        // type: 'quadratic',
        // type: 'polyline',
        style: {
          radius: 30,
          endArrow: {
            fill: '#ccc',
            path: G6.Arrow.vee(10, 15, 0)
          },
          lineWidth: 2,
          stroke: '#ccc'
        }
      },
      layout: {
        type: 'dagre',
        rankdir: 'LR',
        // nodesep: 100,
        ranksep: 100
      },
      fitView: true,
      fitViewPadding: 20,
      enabledStack: true,
      nodeStateStyles: {
        selected: {
          lineWidth: 2,
          stroke: '#1890ff',
          fill: '#e6f7ff'
        },
        active: {
          lineWidth: 2,
          stroke: '#1890ff',
          fill: '#e6f7ff'
        },
        hover: {
          lineWidth: 2,
          stroke: '#1890ff',
          fill: '#e6f7ff'
        }
      },
      edgeStateStyles: {
        active: {
          lineWidth: 5,
          stroke: '#aaa'
        },
        hover: {
          lineWidth: 5,
          stroke: '#aaa'
        }
      },
      modes: {
        default: [
          'drag-canvas',
          'zoom-canvas'
        ],
        edit: [
          'drag-canvas',
          'zoom-canvas',
          'drag-node',
          {
            type: 'tooltip',
            offset: 20,
            formatText(model) {
              return model.name;
            }
          },
          // {
          //   type: 'click-select',
          //   multiple: false
          // },
          {
            type: 'create-edge',
            key: 'shift',
            shouldEnd: shouldEndCreateEdge,
            shouldBegin: shouldBeginCreateEdge
          },
          {
            type: 'activate-relations',
            trigger: 'mouseenter',
            resetSelected: false
          }
        ]
      },
      plugins: [grid]
    }, options));
    // 设置默认字体大小
    LABEL_FONT_SIZE = this.getNodeLabelFontSize();
    // TODO 调试使用
    window.g6Utils = this;
    window.graph = this.graph;
    return this;
  }

  /**
   * 加载数据
   * @param data
   * @param options
   */
  readData(data = { nodes: [], edges: [] }, options = { labelMaxWidth: LABEL_MAX_WIDTH, fontSize: LABEL_FONT_SIZE, stack: false }) {
    const { nodes, edges } = data;
    const ops = Object.assign({
      labelMaxWidth: LABEL_MAX_WIDTH,
      fontSize: LABEL_FONT_SIZE,
      stack: false
    }, options);
    const { labelMaxWidth, fontSize, stack } = ops;
    // 处理过长label
    nodes.forEach(item => {
      if (item.name) {
        item.label = G6Utils.ellipsisString(item.name, labelMaxWidth, fontSize);
      }
    });
    this.graph.read({ nodes, edges });
    if (!stack) {
      // 清除渲染记录
      this.graph.clearStack();
    }
  }

  /**
   * 绑定editModel事件
   * @param graph
   */
  bindEditModelEvent(graph = this.graph) {
    graph.on('edge:mouseenter', e => {
      graph.setItemState(e.item, 'hover', true);
    });
    graph.on('edge:mouseleave', e => {
      graph.setItemState(e.item, 'hover', false);
    });
    /* 边界创建完成后重新布局*/
    graph.on('aftercreateedge', e => {
      graph.layout();
    });
  }

  /**
   * 切换编辑器模式edit/default
   * @param modelType
   * @param graph
   */
  setModelType(modelType = EnumModelType.edit, graph = this.graph) {
    if (modelType === EnumModelType.edit) {
      this.bindEditModelEvent();
      graph.setMode(modelType);
    } else {
      graph.setMode(modelType);
    }
  }

  /**
   * 添加节点
   * @param nodeData
   * @param labelMaxWidth
   * @param fontSize
   */
  addNode(nodeData = { id: '', name: '', x: 0, y: 0 }, labelMaxWidth = LABEL_MAX_WIDTH, fontSize = LABEL_FONT_SIZE) {
    if (nodeData.name) {
      nodeData.label = G6Utils.ellipsisString(nodeData.name, labelMaxWidth, fontSize);
    }
    this.graph.addItem('node', nodeData);
    this.graph.layout();
  }

  /**
   * 更新节点
   * @param id
   * @param model
   * @param stack
   */
  updateNodeItem(id = '', model = { name: '', allowReturn: false, nodeType: 0 }, stack = false) {
    const nodeModel = this.graph.findById(id)._cfg.model;
    model.label = G6Utils.ellipsisString(model.name);
    this.graph.updateItem(id, Object.assign(nodeModel, model), stack);
  }

  /**
   * 获取默认设置节点字体大小
   * @returns {number}
   */
  getNodeLabelFontSize() {
    let fontSize = 12;
    const { defaultNode } = this.graph.cfg;
    if (defaultNode.labelCfg) {
      const { labelCfg } = defaultNode;
      if (labelCfg.style) {
        fontSize = labelCfg.style.fontSize || 12;
      }
    }
    return fontSize;
  }

  /**
   * 重做
   */
  redo() {
    const graph = this.graph;
    const redoStack = graph.getRedoStack();

    if (!redoStack || redoStack.length === 0) {
      return;
    }

    const currentData = redoStack.pop();

    if (currentData) {
      const action = currentData.action;
      let data_2 = currentData.data.after;
      graph.pushStack(action, lodash.cloneDeep(currentData.data));

      if (action === 'delete') {
        data_2 = currentData.data.before;
      }

      if (!data_2) return;

      switch (action) {
        case 'visible':
        {
          Object.keys(data_2).forEach(function(key) {
            const array = data_2[key];
            if (!array) return;
            array.forEach(function(model) {
              const item = graph.findById(model.id);

              if (model.visible) {
                graph.showItem(item, false);
              } else {
                graph.hideItem(item, false);
              }
            });
          });
          break;
        }

        case 'render':
        case 'update':
          Object.keys(data_2).forEach(function(key) {
            const array = data_2[key];
            if (!array) return;
            array.forEach(function(model) {
              graph.updateItem(model.id, model, false);
            });
          });
          break;

        case 'changedata':
          graph.changeData(data_2, false);
          break;

        case 'delete':
          if (data_2.edges) {
            data_2.edges.forEach(function(model) {
              graph.removeItem(model.id, false);
            });
          }

          if (data_2.nodes) {
            data_2.nodes.forEach(function(model) {
              graph.removeItem(model.id, false);
            });
          }

          if (data_2.combos) {
            data_2.combos.forEach(function(model) {
              graph.removeItem(model.id, false);
            });
          }

          break;

        case 'add':
        {
          Object.keys(data_2).forEach(function(key) {
            const array = data_2[key];
            if (!array) return;
            array.forEach(function(model) {
              const itemType = model.itemType;
              delete model.itemType;
              graph.addItem(itemType, model, false);
            });
          });
          break;
        }

        default:
      }
    }
  }

  /**
   * 撤回
   */
  undo() {
    const graph = this.graph;
    const undoStack = graph.getUndoStack();

    if (!undoStack || undoStack.length === 0) {
      return;
    }

    const currentData = undoStack.pop();

    if (currentData) {
      const action = currentData.action;
      graph.pushStack(action, lodash.cloneDeep(currentData.data), 'redo');
      let data_1 = currentData.data.before;

      if (action === 'add') {
        data_1 = currentData.data.after;
      }

      if (!data_1) return;

      switch (action) {
        case 'visible':
        {
          Object.keys(data_1).forEach(function(key) {
            const array = data_1[key];
            if (!array) return;
            array.forEach(function(model) {
              const item = graph.findById(model.id);

              if (model.visible) {
                graph.showItem(item, false);
              } else {
                graph.hideItem(item, false);
              }
            });
          });
          break;
        }

        case 'render':
        case 'update':
          Object.keys(data_1).forEach(function(key) {
            const array = data_1[key];
            if (!array) return;
            array.forEach(function(model) {
              graph.updateItem(model.id, model, false);
            });
          });
          break;

        case 'changedata':
          graph.changeData(data_1, false);
          break;

        case 'delete':
        {
          Object.keys(data_1).forEach(function(key) {
            const array = data_1[key];
            if (!array) return;
            array.forEach(function(model) {
              const itemType = model.itemType;
              delete model.itemType;
              graph.addItem(itemType, model, false);
            });
          });
          break;
        }

        case 'add':
          Object.keys(data_1).forEach(function(key) {
            const array = data_1[key];
            if (!array) return;
            array.forEach(function(model) {
              graph.removeItem(model.id, false);
            });
          });
          break;

        default:
      }
    }
  }
}
/* create-edge 依赖方法(私有)*/
let beginNode = null;
let edges = null;
function shouldEndCreateEdge(e) {
  const endNode = e.item._cfg;
  const edge = lodash.find(edges, { source: beginNode.id, target: endNode.id });
  if (edge) {
    MessageBox.alert('同方向上只能有一组关系!', '提示', {
      type: 'warning'
    });
    return false;
  } else {
    return true;
  }
}
function shouldBeginCreateEdge(e) {
  beginNode = e.item._cfg;
  edges = e.currentTarget.save().edges;
  return true;
}

